#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
#include <fstream>
#include <iostream>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

// Outputting to the Screen
int main(int argc, char *argv[]) {
  // 初始化SDL
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
    std::cout << "error: Could not initialize SDL - " << SDL_GetError()
              << std::endl;
    return 0;
  }

  int ret(0);
  AVFormatContext *formatCtx(nullptr);
  AVCodecContext *codecCtx(nullptr);
  AVFrame *frame(nullptr);
  SDL_Overlay *sdlOverlay(nullptr);

  // 取命令行参数获取文件名
  std::string filename;
  if (argc > 1) {
    filename = argv[1];
  } else {
    filename = "C:/Users/zhou/Videos/bigbuckbunny.mp4";
  }

  // 打开文件
  ret = avformat_open_input(&formatCtx, filename.c_str(), nullptr, nullptr);
  if (ret < 0) {
    std::cout << "error: avformat_open_input() failed" << std::endl;
    goto fail;
  }

  // 初始化流信息
  ret = avformat_find_stream_info(formatCtx, nullptr);
  if (ret < 0) {
    std::cout << "error: avformat_find_stream_info() failed" << std::endl;
    goto fail;
  }

  // 查找视频流
  int videoStream(-1);
  for (uint32_t i = 0; i < formatCtx->nb_streams; ++i) {
    if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      videoStream = i;
      break;
    }
  }

  if (videoStream == -1) {
    std::cout << "error: Didn't find a video stream";
    goto fail;
  }

  // 获取解码器
  const AVCodec *codec =
      avcodec_find_decoder(formatCtx->streams[videoStream]->codecpar->codec_id);
  if (!codec) {
    std::cout << "error: Unsupported codec" << std::endl;
    goto fail;
  }

  // 创建解码器上下文
  codecCtx = avcodec_alloc_context3(codec);
  if (!codecCtx) {
    std::cout << "error: avcodec_alloc_context3() failed" << std::endl;
    goto fail;
  }

  // 使用视频流的编码参数设置解码器上下文
  ret = avcodec_parameters_to_context(
      codecCtx, formatCtx->streams[videoStream]->codecpar);
  if (ret < 0) {
    std::cout << "error: avcodec_parameters_to_context() failed" << std::endl;
    goto fail;
  }

  // 初始化解码器上下文
  ret = avcodec_open2(codecCtx, codec, nullptr);
  if (ret < 0) {
    std::cout << "error: avcodec_open2() failed" << std::endl;
    goto fail;
  }

  // 创建 SDL_Surface
  SDL_Surface *screen =
      SDL_SetVideoMode(codecCtx->width, codecCtx->height, 0, 0);
  if (!screen) {
    std::cout << "error: SDL: could not set video mode" << std::endl;
    goto fail;
  }

  // YV12 SDL_Overlay
  sdlOverlay = SDL_CreateYUVOverlay(codecCtx->width, codecCtx->height,
                                    SDL_YV12_OVERLAY, screen);

  // 创建格式转换上下文
  SwsContext *swsCtx =
      sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
                     codecCtx->width, codecCtx->height, AV_PIX_FMT_YUV420P,
                     SWS_BILINEAR, nullptr, nullptr, nullptr);

  // 创建帧结构，用于接收视频帧
  frame = av_frame_alloc();
  if (!frame) {
    std::cout << "error: av_frame_alloc() failed" << std::endl;
    goto fail;
  }

  // 循环读取 packet
  AVPacket packet;
  int frameFinished(0);
  int i(0);
  while (av_read_frame(formatCtx, &packet) >= 0) {
    if (packet.stream_index == videoStream) {
      // 将 packet 发送给解码器上下文
      ret = avcodec_send_packet(codecCtx, &packet);
      if (ret < 0) {
        std::cout << "error: avcodec_send_packet() failed" << std::endl;
        goto fail;
      }

      // 从解码器上下文中获取解码后的帧
      ret = avcodec_receive_frame(codecCtx, frame);
      if (ret == 0) {
        SDL_LockYUVOverlay(sdlOverlay);

        // 将 SDL_Overlay 的内存指定到 AVFrame 结构
        // YV12 和 YUV420P 格式区别，UV分量顺序不同
        // https://blog.csdn.net/dss875914213/article/details/120836765
        AVFrame frameDst;
        frameDst.data[0] = sdlOverlay->pixels[0];
        frameDst.data[1] = sdlOverlay->pixels[2];
        frameDst.data[2] = sdlOverlay->pixels[1];
        frameDst.linesize[0] = sdlOverlay->pitches[0];
        frameDst.linesize[1] = sdlOverlay->pitches[2];
        frameDst.linesize[2] = sdlOverlay->pitches[1];

        // 执行像素格式转换
        sws_scale(swsCtx, frame->data, frame->linesize, 0, codecCtx->height,
                  frameDst.data, frameDst.linesize);

        SDL_UnlockYUVOverlay(sdlOverlay);

        SDL_Rect rect = {0, 0, (Uint16)codecCtx->width,
                         (Uint16)codecCtx->height};
        SDL_DisplayYUVOverlay(sdlOverlay, &rect);
      }
    }

    av_packet_unref(&packet);

    bool quit(false);

    SDL_Event event;
    SDL_PollEvent(&event);

    switch (event.type) {
    case SDL_QUIT:
      SDL_Quit();
      quit = true;
      break;

    default:
      break;
    }

    if (quit) {
      break;
    }
  }

fail:
  SDL_FreeYUVOverlay(sdlOverlay);
  av_frame_free(&frame);
  sws_freeContext(swsCtx);
  avcodec_free_context(&codecCtx);
  avformat_close_input(&formatCtx);

  return 0;
}